Introdução¶
Imagine um banco de dados de um cinema onde cada linha representa um usuário e cada coluna indica se essa pessoa assistiu ou não a um filme.
Ao analisar essa tabela, podemos identificar padrões. Como a tendência de quem assiste ao Filme A também assistir ao Filme B, e de quem assiste ao Filme C também assistir ao Filme D.
Esses padrões também são chamados de regras de associação que podem ser usadas para a criação de um sistema de recomendação. O algoritmo FP-Growth é utilizado para descobrir essas regras que nos ajudam a determinar quais filmes recomendar a um usuário com base no seu histórico de visualizações
Algoritmo FP-Growth¶
O FP-Growth monta uma estrutura do tipo arvore e com isso a execução dele é mais rápida que o Apriori, por exemplo.
Para explicar o funcionamento do FP-Growth, Considere os seguintes dados em que cada linha é uma transação (T1, T2,.., T5). Imagine uma transação como cada acesso de um usuário ao site MovieLens.
.png?raw=true)
Cada transação tem um conjunto de itens e podemos pensar neles, como o nome dos filmes que usuário avaliou, em cada vez que acessou o site. Para simplificar, ao invés de nome dos filmes, colocaremos somente uma letra.
Por exemplo, na transação T1 o usuário avaliou os filmes {E,K,M,N,O,Y}, as outras linhas seguem a mesma lógica.
1ª Etapa¶
Na primeira etapa de execução do algoritmo FP-Growth, é criado uma tabela, onde a frequência de cada item individual é calculada.
Por exemplo, o item A, só ocorre 1 vez no meu conjunto de transações. Já o item K ocorre 5 vezes.
Portanto, a primeira etapa do algoritmo é a contagem de ocorrências de cada item nas transações.
2ª Etapa¶
Para o próximo passo, vamos considerar um suporte mínimo de 60% e uma confiança de de 80%. Como temos 5 transações, um suporte mínimo de 60% implica em 3 transações (60% ×5). Assim como confiança de 80% implica em 4 transações (80% ×5).
De posse da tabela de frequência, eu vou construir outra tabela cuja frequência dos itens seja maior ou igual ao suporte mínimo.
Neste exemplo, definimos os suportes mínimos como 3, então, só poderemos ter itens com a frequência maior ou igual a 3.
3ª Etapa¶
A partir da tabela criada na etapa anterior, construímos uma lista de ordem descrente de itens cuja a frequência iguala ou supera o suporte. Em seguida, criamos uma nova coluna com estes itens ordenados.
A diferença entre as colunas “Itens” e “Ordered-Item Set” é que a segunda está ordenada pelo suporte, além disso, retiramos os itens com baixo suporte.
4ª Etapa¶
Agora vamos iniciar a construção da arvore, pegaremos cada transação ordenada e vamos criar um “ramo” da nossa árvore cuja raiz é um valor nulo.
Cada parte do ramo tem o item seguido da sua frequência (contagem) até o momento. Primeiro, vamos inserir o conjunto da transação T1 {K,E,M,O,Y}.

Agora vamos inserir ao conjunto da transação T2 {K,E,O,Y}. Já existe um caminho para os itens K e E, então, vamos aproveitá-lo, só adicionando na contagem. Como não há um vinculo entre o caminho E e O, precisamos criar um novo nó para os itens O e Y.
Vamos inserir o conjunto da transação T3 : {K,E.M}. Nós podemos aproveitar o caminho da transação T1, só precisamos adicionar 1 elemento da contagem.
Agora, vamos inserir a transação T4 que é {K,M,Y}. Conseguimos aproveitar o nó K só adicionando 1 elemento na contagem. Em seguida, devemos criar os outros nós para M e Y.
Vamos inserir o conjunto da transação T5 : {K,E,O}. Não precisamos criar um caminho, pois ele já existe, só vamos adicionar 1 elemento em cada contagem.
Para a próxima etapa é necessário termos em mente a árvore gerada na 4ª etapa e o conjunto de itens ordenados na 3ª etapa.
5ª Etapa¶
Criaremos uma tabela onde será calculada a Base Condicional de Padrões (Conditional Pattern Base). Esta base é composta pelas transações que possuem o item mencionado na 1ª coluna (Itens). A 1ª coluna (Itens) está organizada em ordem crescente de suporte.
Vamos criar a tabela em verde, a partir dos resultados gerados nas etapas anteriores.
Por exemplo, a transação T1 tem os itens {K,E,M,O,Y}, ela vai entrar na base condicional de padrões para os itens Y. Para descobrir quais itens estão relacionados com o Y, precisamos olhar o caminho da arvore. Então, adicionamos o conjunto de itens do caminho e o suporte do Y.
Outro exemplo, seria o item O. Ele está presente nas transações T1,T2,T5. Então, eu olho os caminhos que o item O percorreu e seu suporte.
Os demais itens seguem esta regra.
6ª Etapa¶
Agora eu pego a base condicional de padrões e verifico em cada linha quais itens se repetem e também vejo qual é a soma dos suportes individuais.
Por exemplo, na linha do item O, o K e o E se repetem nos dois conjuntos. Então, adicionamos estes dois itens na coluna de Árvore de Padrões Frequentes (Conditional Frequent Pattern Tree).Além disso, a soma dos suportes de K e E é 3.
Para o item K, nenhum elemento foi gerado na etapa anterior, então, ambas colunas ficam vazias.
7ª Etapa¶
Nesta etapa, pegaremos a coluna de itens e a coluna Árvore de Padrões Frequentes e faremos uma combinação e vamos gerar a coluna Frequent Pattern Generated.
Por exemplo, na segunda linha temos o item O e o conjunto {K,E:3}. Podemos combiná-los da seguinte forma:
8ª Etapa¶
Uma vez que eu tenho a coluna Frequent Pattern Generated (Padrões Frequentes Gerados), nós podemos gerar as regras de associação.
Para cada linha, dois tipos de regras de associação podem ser inferidos. Por exemplo, na primeira linha nós podemos ter K → Y ou Y → K. Para determinar a regra válida, a confiança é calculada e a regra com maior confiança maior ou igual ao valor mínimo de confiança é mantida.
Nós temos 3 transações em que os itens K e Y ocorrem. Vamos descobrir qual das duas regras será a escolhida, K → Y ou Y → K:
K → Y significa que quem assiste o Filme K também assiste o filme Y. Neste caso, temos uma confiança de 60%.
$\large\text{c}= \large \frac{\text{Transações que os itens}\ \textbf{K} \ \text{e} \ \textbf{Y}\ \text{ocorrem}}{\text{Total de transações que tem o}\ \textbf{K}} = \frac{3}{5}=0,6$
$\small\text{Sendo,}$
$\small\text{c = confiança}$
Y → K significa que quem assiste o Filme Y também assiste o filme K. A confiança é de 100%.
$\large\text{c}= \large \frac{\text{Transações que os itens}\ \textbf{Y} \ \text{e} \ \textbf{K}\ \text{ocorrem}}{\text{Total de transações que tem o}\ \textbf{K}} = \frac{3}{3}=1$
A confiança de Y → K é maior que a de K → Y. Portanto, a regra escolhida é Y → K. A regras para os demais elementos seguem a mesma lógica.
%load_ext pretty_jupyter
Importar Bibliotecas¶
import pandas as pd
import os
import numpy as np
from mlxtend.frequent_patterns import fpgrowth
from mlxtend.frequent_patterns import association_rules
Definir Diretório¶
os.chdir("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2")
Carregar Arquivos¶
Tabelas de apoio¶
genero_user_treino1 = pd.read_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/3.Datasets_Transformação/3.3_Datasets_Transformação_parte_3/genero_user_treino1.pickle", compression='gzip')
genero_user_teste1 = pd.read_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/3.Datasets_Transformação/3.3_Datasets_Transformação_parte_3/genero_user_teste1.pickle", compression='gzip')
genero_user_treino1
| movieId | title | genres | Ano_do_filme | titulo_sem_ano | genres_separado | userId | Numero_de_Avaliacoes_por_usuarios | Numero_de_Avaliacoes_por_Filme | rating | timestamp | rating_times | rating_medio_simples | rating_medio_ponderado | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | Toy Story (1995) | Adventure|Animation|Children|Comedy|Fantasy | 1995 | Toy Story | Adventure | 144 | 154 | 1848 | 3.5 | 2014-01-12 03:50:37 | 0.515954 | 3.928842 | 3.264437 |
| 1 | 1 | Toy Story (1995) | Adventure|Animation|Children|Comedy|Fantasy | 1995 | Toy Story | Adventure | 304 | 498 | 1848 | 3.0 | 2023-06-01 19:24:35 | 2.453737 | 3.928842 | 3.264437 |
| 2 | 1 | Toy Story (1995) | Adventure|Animation|Children|Comedy|Fantasy | 1995 | Toy Story | Adventure | 461 | 1692 | 1848 | 4.5 | 2020-08-28 20:04:03 | 2.225713 | 3.928842 | 3.264437 |
| 3 | 1 | Toy Story (1995) | Adventure|Animation|Children|Comedy|Fantasy | 1995 | Toy Story | Adventure | 751 | 469 | 1848 | 3.0 | 2012-09-30 20:05:17 | 0.349977 | 3.928842 | 3.264437 |
| 4 | 1 | Toy Story (1995) | Adventure|Animation|Children|Comedy|Fantasy | 1995 | Toy Story | Adventure | 974 | 1160 | 1848 | 5.0 | 1999-09-17 11:01:24 | 0.053903 | 3.928842 | 3.264437 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 820503 | 288557 | Initial D: Third Stage (2001) | Action|Animation|Romance | 2001 | Initial D: Third Stage | Action | 256559 | 803 | 2 | 4.0 | 2023-06-30 15:36:23 | 3.319434 | 4.000000 | 0.140522 |
| 820504 | 288647 | Everybody's Oma (2022) | Documentary | 2022 | Everybody's Oma | Documentary | 218862 | 886 | 1 | 4.5 | 2023-07-05 08:02:09 | 3.743711 | 4.500000 | 0.126201 |
| 820505 | 288669 | Insidious: The Red Door (2023) | Horror|Mystery|Thriller | 2023 | Insidious: The Red Door | Horror | 324508 | 1126 | 1 | 2.5 | 2023-07-15 02:49:04 | 2.090265 | 2.500000 | 0.070112 |
| 820506 | 288679 | The Out-Laws (2023) | Action|Comedy|Romance | 2023 | The Out-Laws | Action | 65065 | 2569 | 1 | 2.5 | 2023-07-09 01:00:03 | 2.084003 | 2.500000 | 0.070112 |
| 820507 | 288761 | Novalis - Die blaue Blume (1993) | Drama | 1993 | Novalis - Die blaue Blume | Drama | 222466 | 571 | 1 | 4.0 | 2023-07-11 09:29:45 | 3.337741 | 4.000000 | 0.112179 |
820508 rows × 14 columns
Tabelas para rodar o algoritmo¶
recomendacao_genero_treino1 = pd.read_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/3.Datasets_Transformação/3.3_Datasets_Transformação_parte_3/recomendacao_genero_treino1.pickle", compression='gzip')
recomendacao_genero_teste1 = pd.read_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/3.Datasets_Transformação/3.3_Datasets_Transformação_parte_3/recomendacao_genero_teste1.pickle", compression='gzip')
recomendacao_genero_treino1
| userId | (no genres listed) | Action | Adventure | Animation | Children | Comedy | Crime | Documentary | Drama | ... | Film-Noir | Horror | IMAX | Musical | Mystery | Romance | Sci-Fi | Thriller | War | Western | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 5 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 |
| 1 | 5 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 | 5 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 |
| 3 | 5 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 4 | 5 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 820503 | 330963 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | ... | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| 820504 | 330963 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | ... | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 |
| 820505 | 330963 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 820506 | 330963 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| 820507 | 330963 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 |
820508 rows × 21 columns
recomendacao_genero_teste1
| userId | (no genres listed) | Action | Adventure | Animation | Children | Comedy | Crime | Documentary | Drama | ... | Film-Noir | Horror | IMAX | Musical | Mystery | Romance | Sci-Fi | Thriller | War | Western | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 128 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
| 1 | 128 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
| 2 | 128 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
| 3 | 128 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
| 4 | 128 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 207989 | 330948 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| 207990 | 330948 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
| 207991 | 330948 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| 207992 | 330948 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 207993 | 330948 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
207994 rows × 21 columns
Modelagem¶
Tentamos criar um modelo com regras de associação de filmes, porém, não foi possível devido ao tamanho do dataset. Como alternativa, fizemos um modelo para recomendação de gênero. Em seguida, vamos aplicar um filtro e obter os filmes com as maiores nota de cada gênero.
Preparar os dados¶
userId está como coluna, mas precisamos que ele seja index.
Verificar o tipo de dados¶
# Verificar o tipo de dados na tabela de treino
recomendacao_genero_treino1.dtypes
userId int64 (no genres listed) int64 Action int64 Adventure int64 Animation int64 Children int64 Comedy int64 Crime int64 Documentary int64 Drama int64 Fantasy int64 Film-Noir int64 Horror int64 IMAX int64 Musical int64 Mystery int64 Romance int64 Sci-Fi int64 Thriller int64 War int64 Western int64 dtype: object
Para economizar memória vamos transformar os dados de int64 para int8.
# Transformar as dummies em int8
recomendacao_genero_treino1= recomendacao_genero_treino1.astype(np.int8)
recomendacao_genero_teste1= recomendacao_genero_teste1.astype(np.int8)
Tranformar coluna em index¶
#Transformar 'userId' em índice
recomendacao_genero_treino1.set_index('userId', inplace=True)
recomendacao_genero_teste1.set_index('userId', inplace=True)
# Verificar se todos os valores são 0 ou 1 (verificar se a transformação int8 deu certo)
valores_binarios_treino = recomendacao_genero_treino1.apply(lambda x: x.isin([0, 1]).all()).all()
print("O DataFrame de treino contém apenas valores binários (0 ou 1)?", valores_binarios)
O DataFrame de treino contém apenas valores binários (0 ou 1)? True
# Verificar se todos os valores são 0 ou 1 (verificar se a transformação int8 deu certo)
valores_binarios_teste = recomendacao_genero_teste1.apply(lambda x: x.isin([0, 1]).all()).all()
print("O DataFrame de teste contém apenas valores binários (0 ou 1)?", valores_binarios)
O DataFrame de teste contém apenas valores binários (0 ou 1)? True
recomendacao_genero_treino1
| (no genres listed) | Action | Adventure | Animation | Children | Comedy | Crime | Documentary | Drama | Fantasy | Film-Noir | Horror | IMAX | Musical | Mystery | Romance | Sci-Fi | Thriller | War | Western | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| userId | ||||||||||||||||||||
| 5 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 |
| 5 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 5 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 |
| 5 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 5 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| -45 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| -45 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 |
| -45 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| -45 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| -45 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 |
820508 rows × 20 columns
recomendacao_genero_teste1
| (no genres listed) | Action | Adventure | Animation | Children | Comedy | Crime | Documentary | Drama | Fantasy | Film-Noir | Horror | IMAX | Musical | Mystery | Romance | Sci-Fi | Thriller | War | Western | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| userId | ||||||||||||||||||||
| -128 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
| -128 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
| -128 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
| -128 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
| -128 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| -60 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| -60 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
| -60 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| -60 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| -60 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
207994 rows × 20 columns
Aplicar o algoritmo e Gerar Regras de Associação¶
# Aplicar algoritmo nos dados de treino
frequent_items_fp = fpgrowth(recomendacao_genero_treino1, min_support=0.01, use_colnames=True)
print("Itens frequentes encontrados:")
print(frequent_items_fp.head())
Itens frequentes encontrados:
support itemsets
0 0.270196 (Thriller)
1 0.080949 (Mystery)
2 0.436996 (Drama)
3 0.167842 (Crime)
4 0.352519 (Comedy)
# Gerar regras de associação
rules_fp = association_rules(frequent_items_fp, metric="lift", min_threshold=0.8)
rules_fp
| antecedents | consequents | antecedent support | consequent support | support | confidence | lift | leverage | conviction | zhangs_metric | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | (Thriller) | (Drama) | 0.270196 | 0.436996 | 0.105986 | 0.392254 | 0.897615 | -0.012089 | 0.926380 | -0.135168 |
| 1 | (Drama) | (Thriller) | 0.436996 | 0.270196 | 0.105986 | 0.242532 | 0.897615 | -0.012089 | 0.963478 | -0.168467 |
| 2 | (Thriller) | (Action) | 0.270196 | 0.301245 | 0.130563 | 0.483216 | 1.604062 | 0.049168 | 1.352122 | 0.516005 |
| 3 | (Action) | (Thriller) | 0.301245 | 0.270196 | 0.130563 | 0.433411 | 1.604062 | 0.049168 | 1.288067 | 0.538934 |
| 4 | (Thriller, Drama) | (Action) | 0.105986 | 0.301245 | 0.031022 | 0.292703 | 0.971643 | -0.000905 | 0.987922 | -0.031613 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 455 | (Adventure) | (Animation, Comedy, Fantasy, Children) | 0.238032 | 0.012210 | 0.011575 | 0.048626 | 3.982634 | 0.008668 | 1.038278 | 0.982862 |
| 456 | (Children) | (Fantasy, Comedy, Adventure, Animation) | 0.086418 | 0.012344 | 0.011575 | 0.133936 | 10.850667 | 0.010508 | 1.140397 | 0.993715 |
| 457 | (Animation) | (Fantasy, Comedy, Adventure, Children) | 0.068840 | 0.014320 | 0.011575 | 0.168136 | 11.741023 | 0.010589 | 1.184905 | 0.982461 |
| 458 | (Fantasy) | (Animation, Comedy, Adventure, Children) | 0.114785 | 0.023537 | 0.011575 | 0.100837 | 4.284243 | 0.008873 | 1.085969 | 0.865989 |
| 459 | (Comedy) | (Animation, Adventure, Fantasy, Children) | 0.352519 | 0.014998 | 0.011575 | 0.032834 | 2.189205 | 0.006287 | 1.018441 | 0.838965 |
460 rows × 10 columns
Associar o id do cliente ao resultado do FP-Growth¶
⚠ 📌 Ao associar os resultados com o userId, notamos que nem todos os clientes possuem recomendações.¶
Função que associa userId e recomendações de gênero¶
def apply_association_rules(user_data, rules)->pd.DataFrame():
''' Função que associa userId e recomendações de gênero.
Esta função é utilizada após a execução do algoritmo FP_Growth()
e a criação das regras de associação.
Args:
- user_data: Um conjunto de dados contendo informações sobre os usuários e seus filmes assistidos.
- rules: Um conjunto de regras de associação geradas pelo algoritmo FP Growth,
que indicam relações frequentes entre gêneros de filmes.
Return:
- A função retorna um pd.DataFrame() com as recomendações para cada usuário
com base nas regras de associação.
'''
recommendations = []
# Iterar user_data agrupado por userId
for user_id, data in user_data.groupby('userId'):
user_preferences = set(data['genres'])
user_recommendations = []
used_genres = set() # Conjunto para controlar gêneros já utilizados
for idx, rule in rules.iterrows():
antecedents = set(rule['antecedents'])
consequents = set(rule['consequents'])
if antecedents.issubset(user_preferences):
for item in consequents:
if item not in user_preferences and item not in used_genres:
user_recommendations.append(item)
used_genres.add(item) # Marca o gênero como utilizado
# Preencher com None se tiver menos de 2 recomendações
while len(user_recommendations) < 3:
user_recommendations.append(None)
recommendations.append({'userId': user_id,
'Recomendacao_genero_1': user_recommendations[0],
'Recomendacao_genero_2': user_recommendations[1],
'Recomendacao_genero_3': user_recommendations[2]})
recommendations_df = pd.DataFrame(recommendations)
return recommendations_df
# Filtrar apenas regras com um lift alto, por exemplo
rules_high_lift = rules_fp[rules_fp['lift'] > 1.5]
# Aplicar regras de associação aos dados de teste
user_recomendacoes_teste1 = apply_association_rules(genero_user_teste1 , rules_high_lift)
# Tabela com as recomendações de gênero
user_recomendacoes_teste1
| userId | Recomendacao_genero_1 | Recomendacao_genero_2 | Recomendacao_genero_3 | |
|---|---|---|---|---|
| 0 | 128 | War | Action | None |
| 1 | 172 | Action | Mystery | Crime |
| 2 | 465 | Romance | Adventure | War |
| 3 | 598 | Action | Mystery | Crime |
| 4 | 919 | Romance | Adventure | War |
| ... | ... | ... | ... | ... |
| 1981 | 330236 | Romance | Adventure | War |
| 1982 | 330321 | Action | Mystery | Crime |
| 1983 | 330496 | None | None | None |
| 1984 | 330667 | Action | Mystery | Crime |
| 1985 | 330948 | Thriller | Crime | Romance |
1986 rows × 4 columns
# Contagens de gêneros recomendados em primeiro lugar
user_recomendacoes_teste1['Recomendacao_genero_1'].value_counts().to_frame()
| count | |
|---|---|
| Recomendacao_genero_1 | |
| Romance | 767 |
| Action | 480 |
| War | 263 |
| Mystery | 118 |
| Thriller | 66 |
| Adventure | 18 |
| Drama | 8 |
| Crime | 3 |
| Comedy | 3 |
| Musical | 2 |
| Children | 1 |
| Fantasy | 1 |
# Verificar quantos valores None existem em cada coluna
none_counts = user_recomendacoes_teste1.isna().sum()
# Exibir os resultados
print("Quantidade de None por coluna:")
print(none_counts)
Quantidade de None por coluna: userId 0 Recomendacao_genero_1 256 Recomendacao_genero_2 256 Recomendacao_genero_3 487 dtype: int64
Modelo 1: .loc[]¶
Abaixo, vamos filtrar os filmes com os maiores rating_medio_ponderado por gênero e associá-los aos resultados encontrados acima.
Filtrar os filmes por gênero¶
A função a seguir filtra os 2 filmes com os maiores rating_medio_ponderado por gênero.
def filtrar_filmes_por_genero(dataframe, generos_unicos):
# Inicializar um dicionário para armazenar os DataFrames filtrados por gênero
filmes_por_genero = {}
# Iterar sobre cada gênero único
for genero in generos_unicos:
# Filtrar os filmes por gênero
filmes_do_genero = dataframe[dataframe['genres_separado'] == genero]
# Ordenar filmes_do_genero pela rating_medio_ponderado em ordem decrescente
filmes_do_genero = filmes_do_genero.sort_values(by='rating_medio_ponderado', ascending=False)
# Remover duplicatas de filmes baseado no título, mantendo apenas o primeiro (com a maior pontuação)
filmes_do_genero = filmes_do_genero.drop_duplicates(subset='title')
# Selecionar os top 2 filmes com as maiores pontuações
top_2_filmes = filmes_do_genero.head(2)
# Adicionar ao dicionário com a chave sendo o nome do gênero
filmes_por_genero[genero] = top_2_filmes
# Retornar o dicionário de DataFrames filtrados por gênero
return filmes_por_genero
# Lista dos gêneros do dataset
lista_generos = list(genero_user_treino1['genres_separado'].unique())
print(lista_generos)
['Adventure', 'Comedy', 'Action', 'Drama', 'Crime', 'Children', 'Mystery', 'Documentary', 'Animation', 'Thriller', 'Horror', 'Fantasy', 'Western', 'Film-Noir', 'Romance', 'Sci-Fi', 'Musical', 'War', '(no genres listed)']
Agora vamos executar a função e para ver se está funcionando, vamos utilizá-la para filtrar os filmes de ação com os maiores rating_medio_ponderado.
# Filtrar os filmes por gênero e armazenar em um dicionário
filmes_por_genero = filtrar_filmes_por_genero(genero_user_treino1, lista_generos )
# Filtrar os filmes de ação
genero_acao = filmes_por_genero['Action']
genero_acao
| movieId | title | genres | Ano_do_filme | titulo_sem_ano | genres_separado | userId | Numero_de_Avaliacoes_por_usuarios | Numero_de_Avaliacoes_por_Filme | rating | timestamp | rating_times | rating_medio_simples | rating_medio_ponderado | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 352621 | 2571 | Matrix, The (1999) | Action|Sci-Fi|Thriller | 1999 | Matrix, The | Action | 192468 | 33 | 2578 | 2.5 | 2016-05-21 11:10:06 | 0.566539 | 4.183670 | 3.630555 |
| 388469 | 2959 | Fight Club (1999) | Action|Crime|Drama|Thriller | 1999 | Fight Club | Action | 110663 | 89 | 2049 | 4.0 | 2018-02-10 22:32:29 | 1.242710 | 4.209614 | 3.549180 |
Associar gênero, filme e userId¶
Vamos unir o resultado do fp_growth() que recomendou gêneros e a filtragem dos 2 filmes com os maiores rating_medio_ponderado por gênero.
# Passo 1: Obter recomendações de filmes por gênero usando a função filtrar_filmes_por_genero
filmes_por_genero_teste = filtrar_filmes_por_genero(genero_user_teste1, lista_generos)
# Passo 2: Criar uma lista para armazenar as recomendações finais
recomendacoes_finais = []
# Passo 3: Iterar sobre cada userId e suas recomendações de gênero e filmes
for user_id, recomendacoes_genero in user_recomendacoes_teste1.iterrows():
user_id = recomendacoes_genero['userId'] # Extraindo o userId corretamente
# Inicializar as recomendações de filmes para este userId
recomendacoes_filmes = {
'userId': user_id,
'Recomendacao_genero_1': recomendacoes_genero['Recomendacao_genero_1'],
'Recomendacao_genero_2': recomendacoes_genero['Recomendacao_genero_2'],
'Recomendacao_genero_3': recomendacoes_genero['Recomendacao_genero_3'],
'Recomendacao_filme_1': None,
'Recomendacao_filme_2': None,
'Recomendacao_filme_3': None,
'Recomendacao_filme_4': None,
'Recomendacao_filme_5': None,
}
# Contador para acompanhar o número de recomendações preenchidas
count_recomendacoes = 0
# Iterar sobre as recomendações de gênero para este userId
for coluna_genero, genero_recomendado in recomendacoes_genero.items():
# Verificar se há um gênero recomendado
if pd.notna(genero_recomendado) and genero_recomendado in filmes_por_genero_teste:
# Pegar os top 2 filmes para este gênero
filmes_genero = filmes_por_genero_teste[genero_recomendado].head(2)['title'].tolist()
# Preencher as recomendações de filmes vazias na ordem até o máximo de 3
for i, filme in enumerate(filmes_genero):
if count_recomendacoes < 5:
if pd.isna(recomendacoes_filmes[f'Recomendacao_filme_{count_recomendacoes + 1}']):
recomendacoes_filmes[f'Recomendacao_filme_{count_recomendacoes + 1}'] = filme
count_recomendacoes += 1
# Adicionar as recomendações deste userId à lista final
recomendacoes_finais.append(recomendacoes_filmes)
# Passo 4: Criar o DataFrame final com as recomendações de filmes e gêneros
recomendacoes_finais_df = pd.DataFrame(recomendacoes_finais)
# Tabela com as recomendações
recomendacoes_finais_df
| userId | Recomendacao_genero_1 | Recomendacao_genero_2 | Recomendacao_genero_3 | Recomendacao_filme_1 | Recomendacao_filme_2 | Recomendacao_filme_3 | Recomendacao_filme_4 | Recomendacao_filme_5 | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 128 | War | Action | None | Run Silent Run Deep (1958) | Greyhound (2020) | Matrix, The (1999) | Fight Club (1999) | None |
| 1 | 172 | Action | Mystery | Crime | Matrix, The (1999) | Fight Club (1999) | Seven (a.k.a. Se7en) (1995) | Twelve Monkeys (a.k.a. 12 Monkeys) (1995) | Shawshank Redemption, The (1994) |
| 2 | 465 | Romance | Adventure | War | Adjustment Bureau, The (2011) | Meet Joe Black (1998) | Lord of the Rings: The Fellowship of the Ring,... | Lord of the Rings: The Two Towers, The (2002) | Run Silent Run Deep (1958) |
| 3 | 598 | Action | Mystery | Crime | Matrix, The (1999) | Fight Club (1999) | Seven (a.k.a. Se7en) (1995) | Twelve Monkeys (a.k.a. 12 Monkeys) (1995) | Shawshank Redemption, The (1994) |
| 4 | 919 | Romance | Adventure | War | Adjustment Bureau, The (2011) | Meet Joe Black (1998) | Lord of the Rings: The Fellowship of the Ring,... | Lord of the Rings: The Two Towers, The (2002) | Run Silent Run Deep (1958) |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1981 | 330236 | Romance | Adventure | War | Adjustment Bureau, The (2011) | Meet Joe Black (1998) | Lord of the Rings: The Fellowship of the Ring,... | Lord of the Rings: The Two Towers, The (2002) | Run Silent Run Deep (1958) |
| 1982 | 330321 | Action | Mystery | Crime | Matrix, The (1999) | Fight Club (1999) | Seven (a.k.a. Se7en) (1995) | Twelve Monkeys (a.k.a. 12 Monkeys) (1995) | Shawshank Redemption, The (1994) |
| 1983 | 330496 | None | None | None | None | None | None | None | None |
| 1984 | 330667 | Action | Mystery | Crime | Matrix, The (1999) | Fight Club (1999) | Seven (a.k.a. Se7en) (1995) | Twelve Monkeys (a.k.a. 12 Monkeys) (1995) | Shawshank Redemption, The (1994) |
| 1985 | 330948 | Thriller | Crime | Romance | Fugitive, The (1993) | Spotlight (2015) | Shawshank Redemption, The (1994) | Silence of the Lambs, The (1991) | Adjustment Bureau, The (2011) |
1986 rows × 9 columns
# Verificar a quantidade de NA (falta de recomendação)
recomendacoes_finais_df.isna().sum()
userId 0 Recomendacao_genero_1 256 Recomendacao_genero_2 256 Recomendacao_genero_3 487 Recomendacao_filme_1 256 Recomendacao_filme_2 256 Recomendacao_filme_3 257 Recomendacao_filme_4 257 Recomendacao_filme_5 487 dtype: int64
Avaliar¶
Para avaliarmos vamos criar uma coluna dummy em que:
- 0 significa que nenhum filme da recomendação foi assistido pelo usuário ;
- 1 significa que pelo menos um dos filmes recomendados foi assistido pelo usuário.
# Função para criar a coluna dummy
def assistiu_ou_nao(row, df_teste):
user_id = row['userId']
recomendacoes = row.drop('userId').values
if len(recomendacoes) == 'None':
return 2
if user_id in df_teste.index:
for filme in recomendacoes:
if filme in df_teste.columns and df_teste.loc[user_id, filme] != 0:
return 1
return 0
# Aplicar a função a cada linha do DataFrame de recomendações
recomendacoes_finais_df['Assistiu'] = recomendacoes_finais_df.apply(assistiu_ou_nao, axis=1, df_teste=recomendacao_genero_teste1)
# Tabela final das recomendações
recomendacoes_finais_df_com_assistiu
| userId | Recomendacao_genero_1 | Recomendacao_genero_2 | Recomendacao_filme_1 | Recomendacao_filme_2 | Recomendacao_filme_3 | Assistiu | |
|---|---|---|---|---|---|---|---|
| 0 | 128 | War | Action | Run Silent Run Deep (1958) | Greyhound (2020) | Matrix, The (1999) | 0 |
| 1 | 172 | Action | Mystery | Matrix, The (1999) | Fight Club (1999) | Seven (a.k.a. Se7en) (1995) | 0 |
| 2 | 465 | Romance | Adventure | Adjustment Bureau, The (2011) | Meet Joe Black (1998) | Lord of the Rings: The Fellowship of the Ring,... | 0 |
| 3 | 598 | Action | Mystery | Matrix, The (1999) | Fight Club (1999) | Seven (a.k.a. Se7en) (1995) | 1 |
| 4 | 919 | Romance | Adventure | Adjustment Bureau, The (2011) | Meet Joe Black (1998) | Lord of the Rings: The Fellowship of the Ring,... | 1 |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 1981 | 330236 | Romance | Adventure | Adjustment Bureau, The (2011) | Meet Joe Black (1998) | Lord of the Rings: The Fellowship of the Ring,... | 0 |
| 1982 | 330321 | Action | Mystery | Matrix, The (1999) | Fight Club (1999) | Seven (a.k.a. Se7en) (1995) | 0 |
| 1983 | 330496 | None | None | None | None | None | 0 |
| 1984 | 330667 | Action | Mystery | Matrix, The (1999) | Fight Club (1999) | Seven (a.k.a. Se7en) (1995) | 0 |
| 1985 | 330948 | Thriller | Crime | Fugitive, The (1993) | Spotlight (2015) | Shawshank Redemption, The (1994) | 0 |
1986 rows × 7 columns
# Salvar a tabela
recomendacoes_finais_df_com_assistiu.to_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/4.Modelagem_FP_Growth/recomendacoes_finais_df_com_assistiu", compression="gzip")
# Avaliar a qualidade das recomendações
recomendacoes_finais_df_com_assistiu[['Assistiu']].value_counts()
Assistiu 0 1599 1 387 Name: count, dtype: int64
O FP-Growth, só conseguiu acertar apenas 19% das recomendações.















